#ifndef XRPL_TX_APPLYCONTEXT_H_INCLUDED
#define XRPL_TX_APPLYCONTEXT_H_INCLUDED

#include <xrpld/app/main/Application.h>
#include <xrpld/core/Config.h>

#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyViewImpl.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/XRPAmount.h>

#include <optional>

namespace ripple {

/** State information when applying a tx. */
class ApplyContext
{
public:
    explicit ApplyContext(
        Application& app,
        OpenView& base,
        std::optional<uint256 const> const& parentBatchId,
        STTx const& tx,
        TER preclaimResult,
        XRPAmount baseFee,
        ApplyFlags flags,
        beast::Journal journal = beast::Journal{beast::Journal::getNullSink()});

    explicit ApplyContext(
        Application& app,
        OpenView& base,
        STTx const& tx,
        TER preclaimResult,
        XRPAmount baseFee,
        ApplyFlags flags,
        beast::Journal journal = beast::Journal{beast::Journal::getNullSink()})
        : ApplyContext(
              app,
              base,
              std::nullopt,
              tx,
              preclaimResult,
              baseFee,
              flags,
              journal)
    {
        XRPL_ASSERT(
            (flags & tapBATCH) == 0, "Batch apply flag should not be set");
    }

    Application& app;
    STTx const& tx;
    TER const preclaimResult;
    XRPAmount const baseFee;
    beast::Journal const journal;

    ApplyView&
    view()
    {
        return *view_;
    }

    ApplyView const&
    view() const
    {
        return *view_;
    }

    // VFALCO Unfortunately this is necessary
    RawView&
    rawView()
    {
        return *view_;
    }

    ApplyFlags const&
    flags() const
    {
        return flags_;
    }

    /** Sets the DeliveredAmount field in the metadata */
    void
    deliver(STAmount const& amount)
    {
        view_->deliver(amount);
    }

    /** Discard changes and start fresh. */
    void
    discard();

    /** Apply the transaction result to the base. */
    std::optional<TxMeta> apply(TER);

    /** Get the number of unapplied changes. */
    std::size_t
    size();

    /** Visit unapplied changes. */
    void
    visit(std::function<void(
              uint256 const& key,
              bool isDelete,
              std::shared_ptr<SLE const> const& before,
              std::shared_ptr<SLE const> const& after)> const& func);

    void
    destroyXRP(XRPAmount const& fee)
    {
        view_->rawDestroyXRP(fee);
    }

    /** Applies all invariant checkers one by one.

        @param result the result generated by processing this transaction.
        @param fee the fee charged for this transaction
        @return the result code that should be returned for this transaction.
     */
    TER
    checkInvariants(TER const result, XRPAmount const fee);

private:
    TER
    failInvariantCheck(TER const result);

    template <std::size_t... Is>
    TER
    checkInvariantsHelper(
        TER const result,
        XRPAmount const fee,
        std::index_sequence<Is...>);

    OpenView& base_;
    ApplyFlags flags_;
    std::optional<ApplyViewImpl> view_;

    // The ID of the batch transaction we are executing under, if seated.
    std::optional<uint256 const> parentBatchId_;
};

}  // namespace ripple

#endif
